# -*- coding: utf-8 -*-
# @Author	: changfeng
# @Date		: 2025/9/13 13:57
# @Last Modified by:   changfeng
# @Last Modified time: 2025/9/13 13:57
# Thanks for your comments!
import hashlib
import json
import traceback
import uuid
import logging
from json import JSONDecodeError
from typing import List, Dict, Any, Optional

from pydantic import ValidationError

from src.basedata.application.commands import SyncDepartments, SyncPersons, SyncMedicines, SyncRouteOfAdministration, \
    SyncAdministrationFreq, SyncDiseaseCode
from src.basedata.domain.service.basedata_save import BaseDataSaveService
from src.basedata.infrastructure.utils.aescoder import aes_decode
from src.basedata.infrastructure.utils.commcon import singleton
from src.basedata.infrastructure.common.unit_of_work import AbstractUnitOfWork
from src.basedata.infrastructure.utils.http_utils import send_sync_get_request
from src.config import CONF

logger = logging.getLogger(__name__)


@singleton
class BaseDataSync(object):
    """
    基础数据同步接口
    """

    def __init__(self, uow: AbstractUnitOfWork):
        self.headers = {'Content-Type': "application/json"}
        self._uow = uow
        self.bds = BaseDataSaveService(uow)

        self.sync_method_map = {
            "department": self.sync_department,
            "users": self.sync_users,
            "route": self.sync_administration_router,
            "freq": self.sync_administration_frequency,
            "disease": self.sync_disease,
            "medicine": self.sync_medicine
        }
        self.dst_method_map = {
            "department": self.bds.save_department,
            "person": self.bds.save_person,
            "administration": self.bds.save_administration,
            "freq": self.bds.save_freq,
            "icd": self.bds.save_icd10,
            "medicine": self.bds.save_medicine
        }

        self.command_map = {
            "department": SyncDepartments,
            "person": SyncPersons,
            "administration": SyncRouteOfAdministration,
            "freq": SyncAdministrationFreq,
            "icd": SyncDiseaseCode,
            "medicine": SyncMedicines
        }

    async def get_sign(self, app_key, params):
        """
        计算签名
        """
        query_str = []
        for key, value in params.items():
            query_str.append("%s=%s" % (key, value))
        query_str = "&".join(query_str) + app_key
        sign_str = hashlib.md5(query_str.encode('utf-8')).hexdigest()
        return sign_str

    async def aes_decoder(self, data, key, iv):
        """
        对获取到的有效数据解密
        :param key: 加密秘钥
        :param data: 需要解密的字符串
        """
        decry_data = aes_decode(data, key, iv)
        return decry_data

    # @tornado.gen.coroutine
    async def save_data_to_database(self, app_info, dst_method, data: List):
        """
        将数据保存到库
        :param app_id: AppId
        :param dst_method: 存储数据的url接口
        :param json_data: 待存储的数据
        """

        return await self.dst_method_map[dst_method](app_info, data)

    async def get_data_from_his(self, hosp_sync_conf, request_uri, dst_name):
        app_name = hosp_sync_conf.get("app").get("name")
        params = {
            'RequestId': str(uuid.uuid1()).replace("-", ""),
            'OrgCode': hosp_sync_conf.get("app").get("code"),
            'AppId': str(hosp_sync_conf.get("app_id")),
        }
        params['Sign'] = await self.get_sign(hosp_sync_conf.get("app").get("appKey"), params)

        options = hosp_sync_conf.get("options")

        new_headers = self.headers
        if options.get("headers", None):
            new_headers.update(options.get("headers"))

        proxies = None
        if options.get("use_proxy", None):
            proxies = CONF.proxies

        if hosp_sync_conf.get("value").endswith("/"):
            request_url = hosp_sync_conf.get("value")[:-1] + request_uri
        else:
            request_url = hosp_sync_conf.get("value") + request_uri

        # request_url = "http://10.246.247.248:18082" + request_uri
        try:
            message = f"从医院:{app_name} 开始获取数据:{request_uri}.请求url: {request_url}, 请求参数: {params}, 请求头: {new_headers}, 代理: {proxies}"
            logger.info(message)
            ret_code, response = await send_sync_get_request(request_url, headers=new_headers, params=params,
                                                             verify_ssl=False, timeout=CONF.default_request_timeout,
                                                             proxy=proxies)
            message = f"从医院:{app_name} 获取数据{request_uri}响应码:{ret_code}, 响应内容:{response[:200]}"
            logger.info(message)
        except Exception as e:
            message = f"请求医院:{app_name} 数据:{request_url}接口异常, error:{str(e)}"
            logger.error(message)
            logger.error(traceback.format_exc())
            return False, message
        if ret_code != 200:
            message = f"请求医院:{app_name} 数据:{request_url}失败，code: {ret_code}，期望200"
            logger.error(message)
            return False, message

        response = json.loads(response)

        if not isinstance(response, dict):
            message = f"医院:{app_name} 数据:{request_uri} 响应内容类型不是对象类型"
            logger.error(message)
            return False, message

        if "Code" not in response or "Msg" not in response or "Data" not in response:
            message = f"医院:{app_name} 数据:{request_uri} 响应内容中缺少'Code' 或 'Msg' 或 'Data' 字段，请检查"
            logger.error(message)
            return False, message

        if response['Code'] != 0:
            message = f"医院:{app_name}, 数据:{request_uri} 响应码校验失败，期望是数字类型0, response:{response}"
            logger.error(message)
            return False, message

        response_data = response['Data']
        encryption = options.get("encryption", False)
        if encryption:  # 数据加了密，需要解密
            secrets = await self.get_secret_key_iv(str(hosp_sync_conf.get("app_id")))
            if not secrets:  # 没有找到，则使用默认值
                secrets = {
                    "iv": "1234567890123456",
                    "key": hosp_sync_conf.get("app").get("appKey")
                }

            try:
                response_data = json.loads(await self.aes_decoder(response_data, secrets.get('key'), secrets.get("iv")))
                logger.info(f"医院: {app_name} 基础数据:{request_uri} 界面成功")
            except JSONDecodeError as e:
                message = f"医院:{app_name} 基础数据：{request_uri} 解密失败，error:{str(e)}"
                logger.error(message)
                logger.error(traceback.format_exc())
                return False, message
        logger.info(f"从医院:{app_name} 获取 ({request_uri}) 数据个数是: {len(response_data)}")
        try:
            save_data = [self.command_map.get(dst_name)(**data) for data in response_data]
        except ValidationError as e:
            message = f"医院：{app_name}：{dst_name}数据通过pydantic解析失败，请检查数据类型, index:{e.errors()} "
            logger.error(message)
            return False, message
        return True, save_data

    async def sync_basedata_by_category(self, hosp_sync_conf, request_uri, dst_name):
        """
        Synchronize the database for the corresponding category
        """
        app_name = hosp_sync_conf.get('app').get('name')

        flag, response_data = await self.get_data_from_his(hosp_sync_conf, request_uri, dst_name)
        if not flag:
            return False, response_data
        batch_index = 0
        for i in range(0, len(response_data), CONF.batch_size):
            chunk = response_data[i:i + CONF.batch_size]
            batch_index += 1
            try:
                logger.info(f"医院:{app_name}, 基础数据: {dst_name} 开始保存， 批次: {batch_index}, 数量:{len(chunk)}")
                await self.save_data_to_database(hosp_sync_conf.get("app"), dst_name, chunk)
            except Exception:
                logger.error(
                    f"保存医院:{app_name} 基础数据:{dst_name} 异常， error: {traceback.format_exc()}")
                continue
        return True, "Success"

    async def sync_department(self, hosp_sync_conf: Dict):
        """
        database sync for department
        """
        request_uri = "/basedata/getdeptlist"
        ret = await self.sync_basedata_by_category(hosp_sync_conf, request_uri, "department")
        return ret

    async def sync_users(self, hosp_sync_conf: Dict):
        """
        同步人员信息
        """
        request_uri = "/basedata/getuserlist"
        ret = await self.sync_basedata_by_category(hosp_sync_conf, request_uri, "person")
        return ret

    async def sync_administration_router(self, hosp_sync_conf: Dict):
        """
        同步给药途径
        """
        request_uri = "/basedata/getadminroutlist"
        ret = await self.sync_basedata_by_category(hosp_sync_conf, request_uri, "administration")
        return ret

    async def sync_administration_frequency(self, hosp_sync_conf: Dict):
        """
        同步给药频次
        """
        request_uri = "/basedata/getadminfreqlist"
        ret = await self.sync_basedata_by_category(hosp_sync_conf, request_uri, "freq")
        return ret

    async def sync_disease(self, hosp_sync_conf: Dict):
        """
        同步疾病分类
        """
        request_uri = "/basedata/getdiseaselist"
        ret = await self.sync_basedata_by_category(hosp_sync_conf, request_uri, "icd")
        return ret

    async def sync_medicine(self, hosp_sync_conf: Dict):
        """
        同步药品目录
        """
        request_uri = "/basedata/getmedicinelist"
        ret = await self.sync_basedata_by_category(hosp_sync_conf, request_uri, "medicine")
        return ret

    async def get_secret_key_iv(self, app_id):
        """
        get data scret key and iv by appid searcn AppConfig
        """
        secret = {}
        obj = await self._uow.app_config.get_xhhk_encrypt_config(app_id=app_id)
        if obj:  # 查到了
            # 获取秘钥和向量
            secret_key = obj.get('options', {}).get("key")
            iv = obj.get('options', {}).get("iv")
            if secret_key and iv:  # 两个都存在的情况下才算有效的
                secret['key'] = secret_key
                secret['iv'] = iv
        return secret

    async def sync_basedata_from_his(self, app_id: Optional[str] = None, method_name: Optional[str] = None,
                                     collection_message: Optional[List[str]] = None):
        """
        查询基础数据配置，同步所有医院的基础数据
        :param app_id: 应用ID
        :param method_name: 指定同步的方法名（可选）
        :param collection_message: 用于收集同步信息的列表（可选）
        """
        async with self._uow:
            app_basedata_confs = await self._uow.app_config.get_basedata_sync_config(app_id=app_id)
            hosp_count = len(app_basedata_confs)
            # 记录总体开始信息
            total_start_msg = f"需要同步基础数据的医院数量是: {hosp_count}"
            logger.info(total_start_msg)
            for hosp in app_basedata_confs:
                try:
                    options = hosp.get("options")
                    hosp_name = hosp.get("app").get("name")
                    info_msg = f"开始同步 {hosp_name} 基础数据"
                    logger.info(info_msg)
                    # if collection_message is not None:
                    #     collection_message.append(info_msg)
                    if not hosp.get("value"):  # 没有这个属性，说明没有配置同步基础数据URL
                        warn_msg = f"医院({hosp_name}) 没有配置基础数据同步url，请检查！"
                        logging.warning(warn_msg)
                        if collection_message is not None:
                            collection_message.append(warn_msg)
                        continue
                    method = options.get("method", None)
                    if not method:
                        warn_msg = f"医院({hosp_name}) 没有配置需要同步的基础数据"
                        logger.warning(warn_msg)
                        if collection_message is not None:
                            collection_message.append(warn_msg)
                        continue
                    if method_name:  # 如果入参中传递了要同步的方法名的话，则替换掉配置中的方法名列表
                        method = [method_name]
                    for method_name in method[::]:
                        func = self.sync_method_map.get(method_name, None)
                        if not func:
                            warn_msg = f"医院:{hosp_name}, 基础数据:{method_name} 同步未找到对应的处理方法."
                            logger.warning(warn_msg)
                            if collection_message is not None:
                                collection_message.append(warn_msg)
                            continue
                        try:
                            result, message = await func(hosp)
                            result_msg = f"医院: {hosp_name}, 同步 {method_name} 完成.result={result}, message={message}"
                            logger.info(result_msg)
                            if collection_message is not None and result is False:
                                collection_message.append(result_msg)
                        except Exception:
                            error_msg = f"医院: {hosp_name}, 同步: {method_name} 失败. 继续下一次基础数据同步. error:{traceback.format_exc()}"
                            logger.error(error_msg)
                            if collection_message is not None:
                                collection_message.append(error_msg)
                            continue
                    info_msg = f"医院: {hosp_name} 所有数据同步结束."
                    logger.info(info_msg)
                except Exception:
                    error_msg = f"医院: {hosp_name} 同步异常，继续下一家医院同步. error:{traceback.format_exc()}"
                    collection_message.append(error_msg)
                    logger.error(error_msg)
                    continue
            # 记录总体完成信息
            total_complete_msg = f"所有医院同步结束，同步的医院数量: {hosp_count}"
            logger.info(total_complete_msg)
